/*
    Copyright 2012 <copyright holder> <email>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

#include "hadrmodel.h"

#ifdef WIN32
typedef SQLWCHAR MYSQLCHAR;
#else
typedef SQLCHAR MYSQLCHAR;
#endif

namespace {

#define TEXT_SIZE 1024

#define CONN_AGAIN "SQL30108N"
#define CONN_AGAIN_CODE -30108

#define QUERYTABLE "SELECT * FROM SYSCAT.TABLES WHERE TABNAME = 'PERSONS'"
#define CREATESQL "CREATE TABLE PERSONS (ID INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY, NAME VARCHAR(100), FAMILYNAME VARCHAR(100))"
#define DROPSQL "DROP TABLE PERSONS"
#define GETPERSONSQUERY "SELECT * FROM PERSONS"
#define ADDPERSON "INSERT INTO PERSONS VALUES(DEFAULT,?,?)"

void copystring(char *dest, const MYSQLCHAR *sou, bool toupper=false) {
    char *pdest = dest;
    for (; *sou; sou++,dest++) *dest = *sou;
    *dest = 0;
    if (toupper) {
        std::locale loc;
        for (; *pdest; pdest++) {
            *pdest = std::toupper(*pdest, loc);
        }
    }
}

void copystring(MYSQLCHAR *dest, const char *sou) {
    for (; *sou; sou++,dest++) *dest = *sou;
    *dest = 0;
}

std::string readSqlError(std::string info,SQLSMALLINT type,SQLHANDLE handle,SQLINTEGER *NativeError) {

//    SQLINTEGER NativeError;
    SQLSMALLINT MsgLen;
    MYSQLCHAR Msg[SQL_MAX_MESSAGE_LENGTH],SqlState[6];
    SQLRETURN ret;
    std::string errs;

    errs = info;
    errs += " ";
    ret = SQLGetDiagRec(type, handle, 1, SqlState, NativeError,
                        Msg, SQL_MAX_MESSAGE_LENGTH-1, &MsgLen);
    if (SQL_SUCCEEDED(ret)) {
        char s1[TEXT_SIZE+1],s2[TEXT_SIZE+1];
        copystring(s1,SqlState);
        copystring(s2,Msg);
        errs += "\n";
        errs += s1;
        errs += " ";
        errs += s2;
    }
    return errs;
}

std::string readSqlError(std::string info,SQLHANDLE handle,SQLINTEGER *NativeError) {
    return readSqlError(info,SQL_HANDLE_DBC,handle,NativeError);
}

SQLRETURN runDirect(SQLHANDLE  StmtHandle, const char *s) {
    MYSQLCHAR stmt[TEXT_SIZE];
    copystring(stmt,s);
    return SQLExecDirect(StmtHandle,stmt,SQL_NTS);
}

SQLRETURN prepareDirect(SQLHANDLE  StmtHandle, const char *s) {
    MYSQLCHAR stmt[TEXT_SIZE];
    copystring(stmt,s);
    return SQLPrepare(StmtHandle, stmt, SQL_NTS);
}

}

class SqlCommand {
    hadrmodel &ha;
protected:
    virtual SQLRETURN command(hadrmodel &ha,SQLHANDLE  StmtHandle) = 0;
    bool errorHandleSTMT;
    
    bool directrunCommand(std::string info) {
        SQLHANDLE  StmtHandle;
	SQLINTEGER NativeError;
	
        if (ha.ConHandle == 0) {
            ha.lastActionStatus = "Not connected";
            return false;
        }
        SQLAllocHandle(SQL_HANDLE_STMT, ha.ConHandle,
                       &StmtHandle);
	errorHandleSTMT = true;
        SQLRETURN RetCode = command(ha,StmtHandle);
	
	bool again = false;
        if (!SQL_SUCCEEDED(RetCode)) {
            if (errorHandleSTMT) {
	      ha.lastActionStatus = readSqlError(info,SQL_HANDLE_STMT,StmtHandle,&NativeError);
	    }
	    else {
	      ha.lastActionStatus = readSqlError(info,SQL_HANDLE_DBC,ha.getConHandle(),&NativeError);
	    }
	    again = (NativeError == CONN_AGAIN_CODE);
        } else {
            SQLINTEGER autocommit;
	    SQLRETURN cliRC = SQLGetConnectAttr(ha.ConHandle, SQL_ATTR_AUTOCOMMIT, &autocommit, 0, NULL);
            if (autocommit == SQL_AUTOCOMMIT_ON) {
	      info += " (autocommit on)";
	    } else {
	      info += " (autocommit off)";
	    }
            ha.lastActionStatus = info + " OK.";
        }
	  
        SQLFreeHandle(SQL_HANDLE_STMT, StmtHandle);
	return again;
    }
    public:
    SqlCommand(hadrmodel &par) : ha(par) {}
    void runCommand(std::string info) {
      if (directrunCommand(info)) {
	directrunCommand(info);
      }
    }

};

class CreatePersonsCommand : public SqlCommand {
    SQLRETURN command(hadrmodel &ha, SQLHANDLE StmtHandle) {
        SQLRETURN RetCode = runDirect(StmtHandle,CREATESQL);
        if (!SQL_SUCCEEDED(RetCode)) {
            return RetCode;
        }
        return SQL_SUCCESS;
    }
    public:
    CreatePersonsCommand(hadrmodel &par) : SqlCommand(par) {};
} ;

class DropPersonsCommand : public SqlCommand {
    SQLRETURN command(hadrmodel &ha, SQLHANDLE StmtHandle) {
        SQLRETURN RetCode = runDirect(StmtHandle,DROPSQL);
        if (!SQL_SUCCEEDED(RetCode)) {
            return RetCode;
        }
        return SQL_SUCCESS;
    }
    public:
    DropPersonsCommand(hadrmodel &par) : SqlCommand(par) {};    
} ;

class QueryCommand : public SqlCommand {
    SQLRETURN command(hadrmodel &ha, SQLHANDLE StmtHandle) {
        SQLRETURN RetCode = runDirect(StmtHandle,GETPERSONSQUERY);
        if (!SQL_SUCCEEDED(RetCode)) {
            return RetCode;
        }

        unsigned long pid;
        SQLCHAR lastName[TEXT_SIZE];
        SQLCHAR name[TEXT_SIZE];

        SQLBindCol(StmtHandle, 1, SQL_INTEGER, (SQLPOINTER)
                   &pid, sizeof(pid), NULL);
        SQLBindCol(StmtHandle, 2, SQL_C_CHAR, (SQLPOINTER)
                   name, sizeof(name), NULL);
        SQLBindCol(StmtHandle, 3, SQL_C_CHAR, (SQLPOINTER)
                   lastName, sizeof(lastName), NULL);
        while (RetCode != SQL_NO_DATA)
        {
            RetCode = SQLFetch(StmtHandle);
            if (RetCode != SQL_NO_DATA) {
                char pname[TEXT_SIZE];
                char pfamilyname[TEXT_SIZE];
                copystring(pname,name);
                copystring(pfamilyname,lastName);
                pList.push_back(person(pid,pname,pfamilyname));
            }
        }
        return SQL_SUCCESS;
    }
public:
    std::vector<person> pList;
    QueryCommand(hadrmodel &par) : SqlCommand(par) {};
};

class LogoutCommand : public SqlCommand {

public:
    LogoutCommand(hadrmodel &par) : SqlCommand(par) {};

private:
    SQLRETURN command(hadrmodel &ha, SQLHANDLE StmtHandle) {

        SQLRETURN RetCode = SQLDisconnect(ha.getConHandle());
        if (!SQL_SUCCEEDED(RetCode)) {
            return RetCode;
        }
        // Free The Connection Handle
        SQLFreeHandle(SQL_HANDLE_DBC, ha.ConHandle);
        // Free The Environment Handle
        SQLFreeHandle(SQL_HANDLE_ENV, ha.EnvHandle);
        ha.ConHandle = 0;
        ha.lastActionStatus = "Ok, disconnected";
        return RetCode;
    }
};

class AutocommitChange : public SqlCommand {
 
  const bool autoon;
  
  SQLRETURN command(hadrmodel &ha, SQLHANDLE StmtHandle) {
       SQLRETURN rc = SQLSetConnectAttr(ha.getConHandle(), SQL_ATTR_AUTOCOMMIT, autoon ? (SQLPOINTER)SQL_AUTOCOMMIT_ON : (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_NTS);    
       return rc;
  }
  
public:
    AutocommitChange(hadrmodel &par,bool par_autoon) : SqlCommand(par),autoon(par_autoon) {};
};

class AddPerson : public SqlCommand {

    std::string name;
    std::string familyName;

    SQLRETURN command(hadrmodel &ha, SQLHANDLE StmtHandle) {
        SQLRETURN RetCode = prepareDirect(StmtHandle, ADDPERSON);
        if (!SQL_SUCCEEDED(RetCode)) {
            return RetCode;
        }
        SQLCHAR lastName[TEXT_SIZE];
        SQLCHAR pname[TEXT_SIZE];
        copystring(pname, name.c_str());
        copystring(lastName,familyName.c_str());
        RetCode = SQLBindParameter(StmtHandle, 1,
                                   SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
                                   sizeof(pname), 0, pname,
                                   sizeof(pname), NULL);
        if (!SQL_SUCCEEDED(RetCode)) {
            return RetCode;
        }
        RetCode = SQLBindParameter(StmtHandle, 2,
                                   SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
                                   sizeof(lastName), 0,lastName,
                                   sizeof(lastName), NULL);
        if (!SQL_SUCCEEDED(RetCode)) {
            return RetCode;
        }
        RetCode = SQLExecute(StmtHandle);
        if (!SQL_SUCCEEDED(RetCode)) {
            return RetCode;
        }
        errorHandleSTMT = false;
        RetCode = SQLEndTran(SQL_HANDLE_DBC, ha.getConHandle(), SQL_COMMIT);
        return RetCode;
    }

public:
    AddPerson(hadrmodel &par, std::string pname, std::string pfamilyName) : name(pname),familyName(pfamilyName),SqlCommand(par) {};


};

void hadrmodel::connect()
{
    if (ConHandle != 0) {
        lastActionStatus = "Already connected.";
        return;
    }

    // Allocate An Environment Handle
    SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE,
                   &EnvHandle);

    // Set The ODBC Application Version To 3.x
    SQLSetEnvAttr(EnvHandle, SQL_ATTR_ODBC_VERSION,
                  (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);

    // Allocate A Connection Handle
    SQLAllocHandle(SQL_HANDLE_DBC, EnvHandle,
                   &ConHandle);
    
// #Driver={IBM DB2 ODBC DRIVER};Database=myDataBase;Hostname=myServerAddress;Port=1234; Protocol=TCPIP;Uid=myUsername;Pwd=myPassword;    

    // Connect To The Appropriate Data Source
    SQLRETURN  RetCode = SQL_SUCCESS;
#define OLD    
#ifdef OLD    
    RetCode = SQLConnect(ConHandle, (SQLCHAR *) "SAMPLE",
                         SQL_NTS, (SQLCHAR *) "db2had1",
                         SQL_NTS, (SQLCHAR *) "db2had1",
                         SQL_NTS);
#else
    char  ConnStrOut [200];
    SQLSMALLINT outLen;
    RetCode = SQLDriverConnect(ConHandle,
                          NULL,
//                          (SQLCHAR *) "Driver={IBM DB2 ODBC DRIVER};Database=HSAMPLE;Hostname=think;Port=50001; Protocol=TCPIP;Uid=db2had1;Pwd=db2had1",
                          (SQLCHAR *) "Driver={IBM DB2 ODBC DRIVER};DSN=HSAMPLE;Uid=db2had1;Pwd=db2had1",
                          SQL_NTS,
                          (SQLCHAR *)ConnStrOut,
                          sizeof(ConnStrOut),
                          &outLen,
                          SQL_DRIVER_PROMPT);
#endif
 
    SQLINTEGER NativeError;
    if (SQL_SUCCEEDED(RetCode)) {
        lastActionStatus = "OK, connected";
    }
    else {
        lastActionStatus = readSqlError("Connecting to database",ConHandle,&NativeError);
        ConHandle = 0;
    }

}

std::vector<person> hadrmodel::getListOfPersons() {
    QueryCommand com(*this);
    com.runCommand("Get list of persons");
    return com.pList;
}

void hadrmodel::disconnect()
{
    LogoutCommand com(*this);
    com.runCommand("Disconnect");
}

void hadrmodel::addPerson(const std::string& name, const std::string& familyName)
{
    AddPerson com(*this,name,familyName);
    com.runCommand("Add person");
}

void hadrmodel::autoCommit(bool on) {
    AutocommitChange com(*this,on);
    com.runCommand(on ? "Autocommit on" : "Autocommit off");
}


void hadrmodel::createPersons() {
    CreatePersonsCommand com(*this);
    com.runCommand("Create table PERSONS");    
}

void hadrmodel::dropPersons() {
  DropPersonsCommand com(*this);
  com.runCommand("Drop table PERSONS");
}

std::string hadrmodel::getConnectionStatus() const {     
    if (ConHandle != 0) { 
       SQLINTEGER autocommit;
       std::string commitInfo;
       SQLRETURN cliRC = SQLGetConnectAttr(ConHandle, SQL_ATTR_AUTOCOMMIT, &autocommit, 0, NULL);
       if (autocommit == SQL_AUTOCOMMIT_ON) {
	commitInfo = " (autocommit on)";
	    } else {
	      commitInfo = " (autocommit off)";
	    }

      return "Connected" + commitInfo;       
    }
    return "Not connected";
  }


